
/**
 * r, g, b 是偏差值, 范围是 0 ~ 3
 */
class Ring {
  /**
   * @param {number} index 
   */
  constructor(index) {
    this.index = index;
    this.r = (index + 0) % 3;
    this.g = (index + 1) % 3;
    this.b = (index + 2) % 3;
    this.a = 0;

    /** 线粗细, 0.0~3.0 之间, 超过 3 可以计算但不推荐 */
    this.gama = 0; // ((index + 2) % 7) / 7
    /** 位置差 */
    this.offX = 0;
    /** 环大小设置 */
    this.size = 12 + (index - 16) * (index - 16) * 0.05;
  }

  /**
   * @param {Float32Array} data
   */
  writeArray(data) {
    const stride = this.index * dataSizePerRing;
    data[stride    ] = this.r;
    data[stride + 1] = this.g;
    data[stride + 2] = this.b;
    data[stride + 3] = this.a;
    data[stride + 4] = this.gama;
    data[stride + 5] = this.offX;
    data[stride + 6] = this.size;
    data[stride + 7] = 0;
  }
}


const vertexShader = `
varying vec2 vUv;
void main(){
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
uniform vec2 iResolution;
uniform float iTime;
uniform sampler2D aTexture;

vec4 getVec4FromTexture(sampler2D arrayTexture, int index) {
  ivec2 dim = textureSize(arrayTexture, 0); // dim = vec(16,2)
  return texture2D(arrayTexture,
    vec2(
      float(index % dim.x) / float(dim.x),
      float(index / dim.x) / float(dim.y)));
}

// use GLSL Preview
void mainImage(out vec4 fragColor, vec2 fragCoord) {
  //Initialize resolution for scaling
  vec2 r=iResolution.xy; // u_resolution = iResolution

  //Save centered pixel coordinates. p=xyInRing
  vec2 xyInRing = (fragCoord-r*.5)*mat2(1,-1,2,1.2);

  //Initialize loop iterator and arc angle
  float a = 0.;
  float b = 0.;
  float fi = 0.;
  vec4 colorV4;
  vec2 xyTmp;

  // 由 js 传的第二片参数
  vec4 pV4;
  float fGama;
  float fOffX;
  float fSize;

  // Loop 32 times
  for(int i = 0; i < 32; i++){
    fi = float(i);

    colorV4 = getVec4FromTexture(aTexture, i * 2) * 255.;
    // colorV4 = texture2D(aTexture, vec2(0./32., float(i)/32.)) * 255.;
    pV4 = getVec4FromTexture(aTexture, i * 2 + 1);
    fGama = pV4.r;
    fOffX = pV4.g;
    fSize = pV4.b;

    xyTmp = (xyInRing + vec2(r.x / 50., r.x / 25.) * (fi + fOffX - 16.))
      / (r.y * 1.8 - xyInRing.y * .1);
    a = atan(xyTmp.y, xyTmp.x) * ceil(fi*.1) + iTime*sin(fi) + fi*fi;
    b = fGama / abs(length(xyTmp) * 80. - fSize);

    //Add with ring attenuation
    fragColor += b*
      //Limit to arcs
      clamp(cos(a),.0,fGama)*
      //Give them color
      (cos(a-fi+colorV4) + vec4(1,1,1,0));
  }   
}

void main() {
  vec4 o = vec4(0, 0, 0, 0);
  mainImage(o, gl_FragCoord.xy);
  gl_FragColor = o;
}
`;

const dataSizePerRing = 8;
const distance = 100;
const mashSize = 100;

class Painter {
  constructor(args) {
    const THREE = this.THREE = args.three;
    /** @type {number} */ this.zIndex = args.zIndex || 0;
    /** @type {Ring[]} */ this.rings = args.rings;

    /** @type {HTMLElement} */ const divMain = args.dom;
    this.width = divMain.clientWidth;
    this.height = divMain.clientHeight;
    const w = this.width, h = this.height;

    const aspect = w / h;
    const fov = Math.atan(((aspect > 1 ? mashSize/aspect : mashSize)/2)/distance) * 2 * (180 / Math.PI);

    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera( fov, aspect, 0.1, 1000 );
    this.camera.position.set( 0, 0, distance );
    this.camera.lookAt( 0, 0, 0 );

    this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    this.renderer.setSize( w, h );

    /** @type {HTMLCanvasElement} */ let canvasDom = this.renderer.domElement;
    canvasDom.style.zIndex = this.zIndex + "";
    divMain.appendChild( canvasDom );
    this.beginRender(vertexShader, fragmentShader);
  }

  /**
   * @param {string} vertexShader 
   * @param {string} fragmentShader 
   */
  beginRender(vertexShader, fragmentShader) {
    const {THREE} = this;

    const data = this.data = new Float32Array(this.rings.length * dataSizePerRing);
    const aTexture = new THREE.DataTexture( data, this.rings.length, dataSizePerRing / 4,
      THREE.RGBAFormat, // format
      THREE.FloatType, // type
    );
    aTexture.magFilter = aTexture.minFilter = THREE.NearestFilter;
    aTexture.wrapS = aTexture.wrapT = THREE.RepeatWrapping;
    aTexture.generateMipmaps = false;
    aTexture.flipY = false;
    aTexture.needsUpdate = true;

    this.iTime = 0;
    const uniforms = this.uniforms = {
      iTime : { value: this.iTime },
      iResolution: { value: new THREE.Vector2(this.width, this.height) },
      aTexture: { value: aTexture },
    };

    const shadertoy = new THREE.ShaderMaterial({
      vertexShader,
      fragmentShader,
      uniforms,
    });

    const box = new THREE.Mesh(
      new THREE.BoxGeometry(mashSize, mashSize, 1),
      shadertoy
    );
    this.scene.add(box);
    this.baseTime = Date.now();
    this.render();
  }

  render() {
    this.iTime = (Date.now() - this.baseTime) / 1000;
    if (this.iTime < 0) {
      this.iTime = 0;
    }
    const iTime = this.uniforms.iTime.value = this.iTime;

    this.rings.forEach((ring, i) => {
      ring.r = (i + 0 + iTime / 600) % 3;
      ring.g = (i + 1 + iTime / 600) % 3;
      ring.b = (i + 2 + iTime / 600) % 3;
      ring.writeArray(this.data);
    });
    this.uniforms.aTexture.value.needsUpdate = true; // 每帧进行修改

    this.renderer.render( this.scene, this.camera );
  }
}

export {
  Painter, Ring,
};
